/*
 * Copyright 2019 Flipkart Internet Pvt. Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.flipkart.ohos.proteus.parser;

import ohos.accessibility.AccessibilityEventInfo;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.element.Element;
import ohos.agp.window.service.Window;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.global.resource.solidxml.Theme;

import com.flipkart.ohos.proteus.HiExecutor;
import com.flipkart.ohos.proteus.LogUtil;
import com.flipkart.ohos.proteus.ProteusConstants;
import com.flipkart.ohos.proteus.ProteusContext;
import com.flipkart.ohos.proteus.ProteusView;
import com.flipkart.ohos.proteus.ViewTypeParser;
import com.flipkart.ohos.proteus.processor.AttributeProcessor;
import com.flipkart.ohos.proteus.processor.BooleanAttributeProcessor;
import com.flipkart.ohos.proteus.processor.DimensionAttributeProcessor;
import com.flipkart.ohos.proteus.processor.DrawableResourceProcessor;
import com.flipkart.ohos.proteus.processor.EventProcessor;
import com.flipkart.ohos.proteus.processor.GravityAttributeProcessor;
import com.flipkart.ohos.proteus.processor.StringAttributeProcessor;
import com.flipkart.ohos.proteus.processor.TweenAnimatorPropertyResourceProcessor;
import com.flipkart.ohos.proteus.toolbox.Attributes;
import com.flipkart.ohos.proteus.value.AttributeResource;
import com.flipkart.ohos.proteus.value.Layout;
import com.flipkart.ohos.proteus.value.ObjectValue;
import com.flipkart.ohos.proteus.value.Resource;
import com.flipkart.ohos.proteus.value.StyleResource;
import com.flipkart.ohos.proteus.value.Value;
import com.flipkart.ohos.proteus.view.ProteusOhosView;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * @author kiran.kumar
 */
public class ViewParser<V extends Component> extends ViewTypeParser<V> {
    private static final String TAG = "ViewParser";
    private static final String ID_STRING_START_PATTERN = "@+id/";
    private static final String ID_STRING_START_PATTERN1 = "@id/";
    private static final String ID_STRING_NORMALIZED_PATTERN = ":id/";
    private static final String HORIZONTAL = "horizontal";
    private static final String VERTICAL = "vertical";
    private static final String BOTH = "both";
    private static final String NONE = "none";
    private static final long DELAY = 1500L;
    private static final int ZERO = 0;
    private static final int DEFAULT_NUM = -1;

    @Override
    public String getType() {
        return "Component";
    }

    @Override
    public String getParentType() {
        return null;
    }

    @Override
    public ProteusView createView(ProteusContext context, Layout layout, ObjectValue data,
                                  ComponentContainer parent, int dataIndex) {
        return new ProteusOhosView(context);
    }

    @Override
    protected void addAttributeProcessors() {
        addAttributeProcessor(Attributes.View.Activated, new BooleanAttributeProcessor<V>() {
            @Override
            public void setBoolean(V view, boolean isActivated) {
                view.setEnabled(isActivated);
            }
        });

        addAttributeProcessor(Attributes.View.Click, new EventProcessor<V>() {
            @Override
            public void setOnEventListener(final V view, final Value value) {
                view.setClickedListener(component -> trigger(Attributes.View.Click, value, (ProteusView) view));
            }
        });

        addAttributeProcessor(Attributes.View.LongClick, new EventProcessor<V>() {
            @Override
            public void setOnEventListener(final V view, final Value value) {
                view.setLongClickedListener(component -> trigger(Attributes.View.LongClick, value, (ProteusView) view));
            }
        });

        addAttributeProcessor(Attributes.View.TouchEvent, new EventProcessor<V>() {
            @Override
            public void setOnEventListener(final V view, final Value value) {
                view.setTouchEventListener((component, touchEvent) -> {
                    trigger(Attributes.View.TouchEvent, value, (ProteusView) view);
                    return false;
                });
            }
        });

        addAttributeProcessor(Attributes.View.DoubleClick, new EventProcessor<V>() {
            @Override
            public void setOnEventListener(final V view, final Value value) {
                view.setDoubleClickedListener(new Component.DoubleClickedListener() {
                    @Override
                    public void onDoubleClick(Component component) {
                        trigger(Attributes.View.DoubleClick, value, (ProteusView) view);
                    }
                });
            }
        });

        addAttributeProcessor(Attributes.View.Background, new DrawableResourceProcessor<V>() {
            @Override
            public void setElement(V view, Element element) {
                view.setBackground(element);
            }
        });

        addAttributeProcessor(Attributes.View.Height, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                ComponentContainer.LayoutConfig layoutParams = view.getLayoutConfig();
                if (layoutParams != null) {
                    layoutParams.height = (int) dimension;
                    view.setLayoutConfig(layoutParams);
                }
            }
        });

        addAttributeProcessor(Attributes.View.Width, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                ComponentContainer.LayoutConfig layoutParams = view.getLayoutConfig();
                if (layoutParams != null) {
                    layoutParams.width = (int) dimension;
                    view.setLayoutConfig(layoutParams);
                }
            }
        });
        addAttributeProcessor(Attributes.View.Weight, new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                DirectionalLayout.LayoutConfig layoutParams = new DirectionalLayout.LayoutConfig(view.getLayoutConfig());
                layoutParams.weight = Float.parseFloat(value);
                view.setLayoutConfig(layoutParams);
            }
        });

        addAttributeProcessor(Attributes.View.LayoutGravity, new GravityAttributeProcessor<V>() {
            @Override
            public void setGravity(V view, int gravity) {
                if (gravity != DEFAULT_NUM) {
                    DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig(view.getLayoutConfig());
                    layoutConfig.alignment = gravity;
                    view.setLayoutConfig(layoutConfig);
                } else {
                    if (ProteusConstants.isLoggingEnabled()) {
                        LogUtil.debug(TAG, "'layout_gravity' is only supported for DirectionalLayout and StackLayout");
                    }
                }
            }
        });

        addAttributeProcessor(Attributes.View.Padding, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                view.setPadding((int) dimension, (int) dimension, (int) dimension, (int) dimension);
            }
        });

        addAttributeProcessor(Attributes.View.PaddingLeft, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                view.setPadding((int) dimension, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom());
            }
        });

        addAttributeProcessor(Attributes.View.PaddingTop, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                view.setPadding(view.getPaddingLeft(), (int) dimension,
                    view.getPaddingRight(), view.getPaddingBottom());
            }
        });

        addAttributeProcessor(Attributes.View.PaddingRight, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), (int) dimension, view.getPaddingBottom());
            }
        });

        addAttributeProcessor(Attributes.View.PaddingBottom, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), (int) dimension);
            }
        });

        addAttributeProcessor(Attributes.View.Margin, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                if (view.getLayoutConfig() != null) {
                    ComponentContainer.LayoutConfig layoutParams = null;
                    layoutParams = view.getLayoutConfig();
                    layoutParams.setMargins((int) dimension, (int) dimension, (int) dimension, (int) dimension);
                    view.setLayoutConfig(layoutParams);
                } else {
                    if (ProteusConstants.isLoggingEnabled()) {
                        LogUtil.debug(TAG, "margins can only be applied to views with parent ComponentContainer");
                    }
                }
            }
        });

        addAttributeProcessor(Attributes.View.MarginLeft, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                if (view.getLayoutConfig() != null) {
                    ComponentContainer.LayoutConfig layoutParams;
                    layoutParams = view.getLayoutConfig();
                    layoutParams.setMargins((int) dimension, layoutParams.getMarginTop(),
                        layoutParams.getMarginRight(), layoutParams.getMarginBottom());
                    view.setLayoutConfig(layoutParams);
                } else {
                    if (ProteusConstants.isLoggingEnabled()) {
                        LogUtil.debug(TAG, "margins can only be applied to views with parent ComponentContainer");
                    }
                }
            }
        });

        addAttributeProcessor(Attributes.View.MarginTop, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                if (view.getLayoutConfig() != null) {
                    ComponentContainer.LayoutConfig layoutParams;
                    layoutParams = view.getLayoutConfig();
                    layoutParams.setMargins(layoutParams.getMarginLeft(),
                        (int) dimension, layoutParams.getMarginRight(), layoutParams.getMarginBottom());
                    view.setLayoutConfig(layoutParams);
                } else {
                    if (ProteusConstants.isLoggingEnabled()) {
                        LogUtil.debug(TAG, "margins can only be applied to views with parent ComponentContainer");
                    }
                }
            }
        });

        addAttributeProcessor(Attributes.View.MarginRight, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                if (view.getLayoutConfig() != null) {
                    ComponentContainer.LayoutConfig layoutParams;
                    layoutParams = view.getLayoutConfig();
                    layoutParams.setMargins(layoutParams.getMarginLeft(),
                        layoutParams.getMarginTop(), (int) dimension, layoutParams.getMarginBottom());
                    view.setLayoutConfig(layoutParams);
                } else {
                    if (ProteusConstants.isLoggingEnabled()) {
                        LogUtil.debug(TAG, "margins can only be applied to views with parent ComponentContainer");
                    }
                }
            }
        });

        addAttributeProcessor(Attributes.View.MarginBottom, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                if (view.getLayoutConfig() != null) {
                    ComponentContainer.LayoutConfig layoutParams;
                    layoutParams = view.getLayoutConfig();
                    layoutParams.setMargins(layoutParams.getMarginLeft(),
                        layoutParams.getMarginTop(), layoutParams.getMarginRight(), (int) dimension);
                    view.setLayoutConfig(layoutParams);
                } else {
                    if (ProteusConstants.isLoggingEnabled()) {
                        LogUtil.debug(TAG, "margins can only be applied to views with parent ComponentContainer");
                    }
                }
            }
        });

        addAttributeProcessor(Attributes.View.MinHeight, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                view.setMinHeight((int) dimension);
            }
        });

        addAttributeProcessor(Attributes.View.MinWidth, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                view.setMinWidth((int) dimension);
            }
        });

        addAttributeProcessor(Attributes.View.Elevation, new DimensionAttributeProcessor<V>() {
            @Override
            public void setDimension(V view, float dimension) {
                //view.setElevation(dimension);
            }
        });

        addAttributeProcessor(Attributes.View.Alpha, new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                view.setAlpha(ParseHelper.parseFloat(value));
            }
        });

        addAttributeProcessor(Attributes.View.Visibility, new AttributeProcessor<V>() {
            @Override
            public void handleValue(V view, Value value) {
                if (value.isPrimitive() && value.getAsPrimitive().isNumber()) {
                    view.setVisibility(value.getAsInt());
                } else {
                    process(view, precompile(value, view.getContext(),
                        ((ProteusContext) view.getContext()).getFunctionManager()));
                }
            }

            @Override
            public void handleResource(V view, Resource resource) {
                Integer visibility = resource.getInteger(view.getContext());
                view.setVisibility(null != visibility ? visibility : Component.HIDE);
            }

            @Override
            public void handleAttributeResource(V view, AttributeResource attribute) {
                Theme theme = attribute.applyTheme(view.getContext());
                try {
                    view.setVisibility(theme.getIntegerValue(view.getName(), Component.HIDE));
                } catch (IOException | NotExistException | WrongTypeException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void handleStyleResource(V view, StyleResource style) {
                Theme theme = style.applyTheme(view.getContext());
                try {
                    view.setVisibility(theme.getIntegerValue(view.getName(), Component.HIDE));
                } catch (IOException | NotExistException | WrongTypeException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public Value compile(Value value, Context context) {
                int visibility = ParseHelper.parseVisibility(value);
                return ParseHelper.getVisibility(visibility);
            }
        });

        addAttributeProcessor(Attributes.View.Id, new StringAttributeProcessor<V>() {
            @Override
            public void setString(final V view, String value) {
                if (view instanceof ProteusView) {
                    view.setId(((ProteusView) view).getViewManager().getContext().getInflater().getUniqueViewId(value));
                }
                final String resourceName = value;
                Window window = new Window();
                window.setAccessibilityEventListener(new Window.IAccessibilityEventListener() {
                    @Override
                    public void onDispatchEvent(AccessibilityEventInfo accessibilityEventInfo) {
                        String normalizedResourceName;
                        if (resourceName.isEmpty()) {
                            String id;
                            if (resourceName.startsWith(ID_STRING_START_PATTERN)) {
                                id = resourceName.substring(ID_STRING_START_PATTERN.length());
                            } else if (resourceName.startsWith(ID_STRING_START_PATTERN1)) {
                                id = resourceName.substring(ID_STRING_START_PATTERN1.length());
                            } else {
                                id = resourceName;
                            }
                            normalizedResourceName = view.getContext()
                                .getBundleName() + ID_STRING_NORMALIZED_PATTERN + id;
                        } else {
                            normalizedResourceName = "";
                        }
                        view.setName(normalizedResourceName);
                    }
                });
            }
        });

        addAttributeProcessor(Attributes.View.ContentDescription, new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                view.setComponentDescription(value);
            }
        });

        addAttributeProcessor(Attributes.View.Clickable, new BooleanAttributeProcessor<V>() {
            @Override
            public void setBoolean(V view, boolean isClickable) {
                view.setClickable(isClickable);
            }
        });

        addAttributeProcessor(Attributes.View.Tag, new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                view.setTag(value);
            }
        });

        addAttributeProcessor(Attributes.View.Enabled, new BooleanAttributeProcessor<V>() {
            @Override
            public void setBoolean(V view, boolean isEnabled) {
                view.setEnabled(isEnabled);
            }
        });

        addAttributeProcessor(Attributes.View.Selected, new BooleanAttributeProcessor<V>() {
            @Override
            public void setBoolean(V view, boolean isSelected) {
                view.setSelected(isSelected);
            }
        });

        addAttributeProcessor(Attributes.View.Style, new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                ProteusView.Manager viewManager = ((ProteusView) view).getViewManager();
                ProteusContext context = viewManager.getContext();
                Layout layout = viewManager.getLayout();

                ViewTypeParser handler = context.getInflater().getParser(layout.type);

                String[] styleSet = value.split(ProteusConstants.STYLE_DELIMITER);
                for (String styleName : styleSet) {
                    Map<String, Value> style = context.getStyle(styleName);
                    if (null != style) {
                        process(context.getStyle(styleName), (ProteusView) view,
                            handler != null ? handler : ViewParser.this);
                    }
                }
            }

            private void process(Map<String, Value> style, ProteusView proteusView, ViewTypeParser handler) {
                for (Map.Entry<String, Value> entry : style.entrySet()) {
                    //noinspection unchecked
                    handler.handleAttribute(proteusView.getAsView(),
                        handler.getAttributeId(entry.getKey()), entry.getValue());
                }
            }
        });

        addAttributeProcessor(Attributes.View.TransitionName, new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                view.setName(value);
            }
        });

        addAttributeProcessor(Attributes.View.RequiresFadingEdge, new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                switch (value) {
                    case NONE:
                        //view.setVerticalFadingEdgeEnabled(false);
                        //view.setHorizontalFadingEdgeEnabled(false);
                        break;
                    case BOTH:
                        //view.setVerticalFadingEdgeEnabled(true);
                        //view.setHorizontalFadingEdgeEnabled(true);
                        break;
                    case VERTICAL:
                        //view.setVerticalFadingEdgeEnabled(true);
                        //view.setHorizontalFadingEdgeEnabled(false);
                        break;
                    case HORIZONTAL:
                        //view.setVerticalFadingEdgeEnabled(false);
                        //view.setHorizontalFadingEdgeEnabled(true);
                        break;
                    default:
                        //view.setVerticalFadingEdgeEnabled(false);
                        //view.setHorizontalFadingEdgeEnabled(false);
                        break;
                }
            }
        });

        addAttributeProcessor(Attributes.View.FadingEdgeLength, new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                view.setFadeEffectBoundaryWidth(ParseHelper.parseInt(value));
            }
        });

        addAttributeProcessor(Attributes.View.AnimatorProperty, new TweenAnimatorPropertyResourceProcessor<V>() {
            @Override
            public void setAnimatorProperty(V view, AnimatorGroup animatorGroup) {
                for (int i = 0; i < animatorGroup.getRoundCount(); i++) {
                    List<Animator> list = animatorGroup.getAnimatorsAt(i);
                    AnimatorProperty animatorProperty = (AnimatorProperty) list.get(ZERO);
                    animatorProperty.setTarget(view);
                }
                HiExecutor.runBg(() -> {
                    try {
                        Thread.sleep(DELAY);
                        animatorGroup.start();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        });
        addAttributeProcessor(Attributes.View.Above,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.ABOVE));
        addAttributeProcessor(Attributes.View.AlignBaseline,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.ALIGN_BASELINE));
        addAttributeProcessor(Attributes.View.AlignBottom,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.ALIGN_BOTTOM));
        addAttributeProcessor(Attributes.View.AlignLeft,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.ALIGN_LEFT));
        addAttributeProcessor(Attributes.View.AlignRight,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.ALIGN_RIGHT));
        addAttributeProcessor(Attributes.View.AlignTop,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.ALIGN_TOP));
        addAttributeProcessor(Attributes.View.Below,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.BELOW));
        addAttributeProcessor(Attributes.View.LeftOf,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.LEFT_OF));
        addAttributeProcessor(Attributes.View.RightOf,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.RIGHT_OF));

        addAttributeProcessor(Attributes.View.AlignEnd,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.ALIGN_END));
        addAttributeProcessor(Attributes.View.AlignStart,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.ALIGN_START));
        addAttributeProcessor(Attributes.View.EndOf,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.END_OF));
        addAttributeProcessor(Attributes.View.StartOf,
            createRelativeLayoutRuleProcessor(DependentLayout.LayoutConfig.START_OF));
        addAttributeProcessor(Attributes.View.AlignParentStart,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.ALIGN_PARENT_START));
        addAttributeProcessor(Attributes.View.AlignParentEnd,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.ALIGN_PARENT_END));

        addAttributeProcessor(Attributes.View.AlignParentTop,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.ALIGN_PARENT_TOP));
        addAttributeProcessor(Attributes.View.AlignParentRight,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.ALIGN_PARENT_RIGHT));
        addAttributeProcessor(Attributes.View.AlignParentBottom,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.ALIGN_PARENT_BOTTOM));
        addAttributeProcessor(Attributes.View.AlignParentLeft,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.ALIGN_PARENT_LEFT));
        addAttributeProcessor(Attributes.View.HorizontalCenter,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.HORIZONTAL_CENTER));
        addAttributeProcessor(Attributes.View.VerticalCenter,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.VERTICAL_CENTER));
        addAttributeProcessor(Attributes.View.CenterInParent,
            createRelativeLayoutBooleanRuleProcessor(DependentLayout.LayoutConfig.CENTER_IN_PARENT));
    }

    private AttributeProcessor<V> createRelativeLayoutRuleProcessor(final int rule) {
        return new StringAttributeProcessor<V>() {
            @Override
            public void setString(V view, String value) {
                if (view instanceof ProteusView) {
                    int id = ((ProteusView) view).getViewManager().getContext().getInflater().getUniqueViewId(value);
                    ParseHelper.addRelativeLayoutRule(view, rule, id);
                }
            }
        };
    }

    private AttributeProcessor<V> createRelativeLayoutBooleanRuleProcessor(final int rule) {
        return new BooleanAttributeProcessor<V>() {
            @Override
            public void setBoolean(V view, boolean isZero) {
                if (isZero){
                    ParseHelper.addRelativeLayoutRule(view, rule, ZERO);
                }
            }
        };
    }

    @Override
    public boolean handleChildren(V view, Value children) {
        return false;
    }

    @Override
    public boolean addView(ProteusView parent, ProteusView view) {
        return false;
    }
}
